<?php

namespace Support;

class Websocket {
    protected $socket;
    protected $clients = [];
    protected $user_manager;

    public function __construct()
    {
        // 用户管理
        $this->user_manager = new UserManager();

        // 创建主 Socket
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, true);
        // 绑定主 Socket 到指定的主机和端口
        socket_bind($this->socket, 0, 8888);
        // 监听连接
        socket_listen($this->socket);

        // 创建客户端 Socket 数组
        $this->clients[] = $this->socket;

        $app_log = new Log('app.log');

        // 等待客户端连接和数据传输
        while (true) {
            $read = $this->clients;
            // 使用 socket_select 函数检查可读的 socket
            // 如果有可读的 socket，返回的数组将包含这些 socket
            // 超时设置为 null，使 select 函数一直等待
            socket_select($read, $write, $except, null);

            foreach ($read as $sock_index => $sock) {
                // 如果主 Socket 有传入连接请求
                if ($sock == $this->socket) {
                    // 接受连接请求并创建客户端 Socket
                    $client = socket_accept($sock);

                    // 将客户端 Socket 添加到客户端数组中
                    $this->clients[] = $client;

                    // 登录
                    $this->user_manager->login($client);
                } else {
                    $user = &$this->user_manager->getUserInfo($sock);

                    // 读取客户端 Socket 的数据
                    $data = socket_read($sock, 102400);

                    if (!$data) {
                        continue;
                    }

                    $app_log->write("user count:  " . count($this->user_manager->getUserList()));
                    $app_log->write("user name:  " . $user['name']);
                    $app_log->write("user handshake:  " . $user['handshake']);

                    // 用户端断开连接
                    if ((ord($data[0]) & 0xf) == 0x8) {
                        $this->close($user);
                        $app_log->write("{$sock_index} close");
                        continue;
                    }

                    if (!$user['handshake']) {
                        // 握手
                        $this->handshake($sock_index, $data, $user);
                        $app_log->write("{$sock_index} handshake success");

                        foreach ($this->user_manager->getUserList() as $it) {
                            $app_log->write("handshake user list handshake:  " . $it['name'] . ':' . $it['handshake']);
                        }
                    } else {
                        // 已握手
                        $this->doMessage($user, $data);
                        $app_log->write("{$sock_index} doMessage success");
                    }
                }
            }
        }
    }

    /**
     * 关闭连接
     * 
     * @param $user
     */
    protected function close(&$user)
    {
        $login_log = new Log('login.log');
        $user_name = $user['name'] ?? DEFAULT_USER_NAME;
        socket_close($user['socket']);
        $socket_key = array_search($user['socket'], $this->clients);
        unset($this->clients[$socket_key]);
        $this->user_manager->logout($user);

        foreach ($this->user_manager->getUserList() as $user) {
            $users[] = $user['name'];
        }

        $res = [
            'type' => 'close',
            'users' => $users,
            'msg' => $user_name . '已退出',
            'time' => date('Y-m-d H:i:s')
        ];

        $login_log->write("{$user_name}: logout");
        $this->sendAllUser($res);
    }

    /**
     * 握手
     * 
     * @param $sock_index
     * @param $request
     * @param &$user
     */
    protected function handshake($sock_index, $request, &$user)
    {
        preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $request, $match);
        $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        $response = "HTTP/1.1 101 Switching Protocols\r\n";
        $response .= "Upgrade: websocket\r\n";
        $response .= "Connection: Upgrade\r\n";
        $response .= "Sec-WebSocket-Accept: {$key}\r\n\r\n";
        socket_write($this->clients[$sock_index], $response);
        $this->user_manager->handshake($user);
    }

    /**
     * 接收并处理消息
     *
     * @param &$user
     * @param $msg
     */
    public function doMessage(&$user, $msg)
    {
        $message_log = new Log('message.log');
        $login_log = new Log('login.log');
        $app_log = new Log('app.log');
        $msg = $this->decode($msg);
        $app_log->write($msg);
        $msg = json_decode($msg, true);

        if (!isset($msg['type'])) {
            return;
        }

        switch ($msg['type']) {
            case 'login': // 登录
                $user_name = $msg['name'] ?? DEFAULT_USER_NAME;
                $this->user_manager->setUserName($user, $user_name);
                $msg = "{$user_name}: login success";
                $users = [];

                foreach ($this->user_manager->getUserList() as $user) {
                    $users[] = $user['name'];
                }

                $res = [
                    'type' => 'login',
                    'name' => $user_name,
                    'msg' => $msg,
                    'users' => $users,
                ];

                $login_log->write("{$user_name}: login");
                $this->sendAllUser($res);

                break;
            case 'message': // 接收并发送消息
                $user_name = $user['name'] ?? DEFAULT_USER_NAME;
                $msg = $msg['msg'] ?? '';

                $res = [
                    'type' => 'message',
                    'name' => $user_name,
                    'msg' => $msg,
                    'time' => date('H:i:s'),
                ];

                $message_log->write("{$user_name}: {$msg}");
                $this->sendAllUser($res);

                break;
        }
    }

    /**
     * 发送给所有人
     *
     */
    protected function sendAllUser($msg)
    {
        if (is_array($msg)) {
            $msg = json_encode($msg);
        }

        $msg = $this->encode($msg);

        foreach ($this->clients as $socket) {
            if ($socket != $this->socket) {
                socket_write($socket, $msg, strlen($msg));
            }
        }
    }

    /**
     * 解码
     *
     * @param $buffer
     * @return string
     */
    protected function decode($buffer)
    {
        $len = ord($buffer[1]) & 127;

        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else {
            if ($len === 127) {
                $masks = substr($buffer, 10, 4);
                $data = substr($buffer, 14);
            } else {
                $masks = substr($buffer, 2, 4);
                $data = substr($buffer, 6);
            }
        }

        $dataLength = strlen($data);
        $masks = str_repeat($masks, floor($dataLength / 4)) . substr($masks, 0, $dataLength % 4);
        return $data ^ $masks;
    }

    protected function encode($buffer)
    {
        if (!is_scalar($buffer)) {
            throw new Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. ");
        }

        $len = strlen($buffer);
        $first_byte = "\x81";

        if ($len <= 125) {
            $encode_buffer = $first_byte . chr($len) . $buffer;
        } else {
            if ($len <= 65535) {
                $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer;
            } else {
                $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer;
            }
        }

        return $encode_buffer;
    }
}
